package cz.muni.fi.pb138.mathml_canonicalizator;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * The base class of MathML Normalization application. It says which elements
 * shall be normalized and governs the normalization process.
 * 
 * @author ndru
 */
public class MathMLParser extends DefaultHandler {

    private Node root;
    private Stack<Transformer> transformers = new Stack<Transformer>();
    private Map<String, TransformerFactory> factories = new HashMap<String, TransformerFactory>();
    // private TransformerFactory defaultFactory = new CopyTransformerFactory();
    private TransformerFactory defaultFactory;
    private static final String configFile = "config.properties";

    public MathMLParser() {

        List<String> toNormalize = Util.getProperty("normalize", configFile);
        List<String> toDestroy = Util.getProperty("destroy", configFile);
        List<String> toErase = Util.getProperty("erase", configFile);
        Properties properties = new Properties();
        
        // getting list of properties for CopyTransformerFactory
        defaultFactory = new CopyTransformerFactory(
                Util.getAllProperties("copy.properties", properties));

        // now for other elements mentioned in toNormalize
        Map<String, Map<String, List<String>>> propertiesPerTag = 
                new HashMap<String, Map<String, List<String>>>();

        for (String node : toNormalize) {
            Map<String, List<String>> propertyMap = 
                    Util.getAllProperties(node + ".properties", properties);

            propertiesPerTag.put(node, propertyMap);
        }

        // All supported Transformers should be listed here.
        for (String node : toNormalize) {
            if (node.equals("msubsup")) {
                factories.put("msubsup", new MsubsupTransformerFactory(propertiesPerTag.get("msubsup")));
            }
            if (node.equals("mfrac")) {
                factories.put("mfrac", new MfracTransformerFactory(propertiesPerTag.get("mfrac")));
            }
            if (node.equals("msqrt")) {
                factories.put("msqrt", new MsqrtTransformerFactory(propertiesPerTag.get("msqrt")));
            }
            if (node.equals("mroot")) {
                factories.put("mroot", new MrootTransformerFactory(propertiesPerTag.get("mroot")));
            }
            if (node.equals("mfenced")) {
                factories.put("mfenced", new MfencedTransformerFactory(propertiesPerTag.get("mfenced")));
            }
        }

        // toDestroy and toErase should always be not null
        if (toDestroy != null) {
            for (String node : toDestroy) {
                factories.put(node, new DestroyerTransformerFactory());
            }
        }
        if (toErase != null) {
            for (String node : toErase) {
                factories.put(node, new EraserTransformerFactory());
            }
        }
    }

    public Node parse(String fileName, boolean isFileName) {
        try {
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();

            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
            
            if (isFileName) {
                saxParser.parse(classLoader.getClass().getResourceAsStream(fileName), this);
            } else {
                saxParser.parse(new ByteArrayInputStream(fileName.getBytes()), this);
            }

        } catch (ParserConfigurationException ex) {
            Logger.getLogger(MathMLParser.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SAXException ex) {
            Logger.getLogger(MathMLParser.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(MathMLParser.class.getName()).log(Level.SEVERE, null, ex);
        }

        return root;
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

        TransformerFactory tf = factories.containsKey(qName)
                ? factories.get(qName)
                : defaultFactory;


        // FIXME: attributes
        Transformer t = tf.newTransformer(qName, Util.attributesToMap(attributes));
        transformers.add(t);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        String text = new String(ch, start, length);
        transformers.peek().setTextContent(text.trim());
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        Transformer t = transformers.pop();
        Node result = t.transform();


        if (transformers.size() > 0) {
            transformers.peek().addChild(result);
        } else {
            root = result;
        }
    }
}
